package com.heima.article.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.heima.article.entity.ApArticle;
import com.heima.article.service.IApArticleService;
import com.heima.article.service.IHotArticleService;
import com.heima.behavior.dto.ArticleAggregatorResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @author: itheima
 * @create: 2022-08-07 12:15
 */
@Service
public class HotArticleServiceImpl implements IHotArticleService {

    @Autowired
    private IApArticleService apArticleService;


    @Autowired
    private StringRedisTemplate redisTemplate;    // StringRedisTemplate是RedisTemplate的子类 也可用但要求必须是string


    /** 从 阅读 点赞 评论 收藏 四个维度 不同权重计算 文章的分值
     * 查询最近5天发布文章；遍历文章计算文章得分；将计算文章（文章就是缓存到zset的元素）以及得分（分值）缓存到Redis Zset数据结构
     */
    @Override
    public void compute() {
        //1.查询最近5天发布文章
        LambdaQueryWrapper<ApArticle> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ApArticle::getIsDelete, false);
        queryWrapper.eq(ApArticle::getIsDown, false);
        Date fromDate = getFromAndToDate(-30);//从什么时候开始查    不查-5 因最近5天还没数据
        Date toDate = getFromAndToDate(0); //0 是 查到当天
        queryWrapper.between(ApArticle::getPublishTime, fromDate, toDate);
        List<ApArticle> list = apArticleService.list(queryWrapper);

        //2.历文章计算文章得分     集合 对象 变量 都需判空
        if (!CollectionUtils.isEmpty(list)) { //不空就遍历取值
            for (ApArticle article : list) {
                //得到当前文章分值
                int score = computeScore(article);
                if (score > 0) { //分值多少是热点文章  此处设 大于0 的都属于热点
                    //3.将热点文章存入Redis缓存
                    String redisKeyPrefix = "leadnews:article:hot:page:";   //前缀一致
                    //3.1 将文章存入"首页key"  存首页的推荐
                    String indexKey = redisKeyPrefix + "0"; // 0是指首页的推荐    相当于leadnews:article:hot:page:0 --推荐
               //构建缓存对象      将当前文章的取出来 再存入 缓存对象  本身查出来的就是未下架和未删除的 so此字段无需再写   只要能展示文章列表即可无需把所有都存redis
                    ApArticle cache = new ApArticle();
                    cache.setId(article.getId());
                    cache.setTitle(article.getTitle());
                    cache.setImages(article.getImages());
                    cache.setAuthorId(article.getAuthorId());
                    cache.setAuthorName(article.getAuthorName());
                    cache.setLayout(article.getLayout());
                    cache.setStaticUrl(article.getStaticUrl());

                    String articleJson = JSON.toJSONString(cache); //这是存 注意存时 将对象转成了字符串 那要是取则需要反序列化即把字符串转成对象 对应ApArticleServiceImpl
                    redisTemplate.opsForZSet().add(indexKey, articleJson, score); //indexKey指明是 推荐  articleJson是要缓存的文章 core是要缓存的分数
                    redisTemplate.expire(indexKey, 1, TimeUnit.DAYS);//设置失效时间否则默认永久有效，设为1天 因为过了24小时任务会重新启动

                    //3.2 将文章存入"频道key"               频道Id  指明是哪个频道的
                    String channelKey = redisKeyPrefix + article.getChannelId();
                    redisTemplate.opsForZSet().add(channelKey, articleJson, score);
                    redisTemplate.expire(channelKey, 1, TimeUnit.DAYS);
                }
            }
        }
    }




    /**
     * 获取指定天数日期 正数：未来时间  负数：以前时间
     *
     * @param datecount
     * @return
     */
    public static Date getFromAndToDate(Integer datecount) {
        //创建日期对象
        Calendar calendar = Calendar.getInstance();
        //设置日期  假如传入的datecount是0则 时间是 当天时间如何0时0分0秒如 07 00:00:00  7号
        calendar.add(Calendar.DATE, datecount);
        //设置时分秒
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        return calendar.getTime();
    }

    /**
     * 为入参的文章计算分值    权重 1 3 5 8
     *
     * @param article
     * @return
     */
    private int computeScore(ApArticle article) {
        int score = 0;
        // 阅读 +1  点赞 +3  评论 +5  收藏 +8
        if (article.getViews() != null) {
            score += article.getViews() * 1;
        }
        if (article.getLikes() != null) {
            score += article.getLikes() * 3;
        }
        if (article.getComment() != null) {
            score += article.getComment() * 5;
        }
        if (article.getCollection() != null) {
            score += article.getCollection() * 8;
        }
        return score;
    }



    /**
     * 根据实时计算文章增量分值，更新redis缓存 热点文章排行旁 ；数据库中 文章表
     *
     * @param result
     */
    @Override
    public void updateCacheAndDB(ArticleAggregatorResult result) {
        //1.计算当日操作行为产生增量得分
        Long plusScore = computeScore(result);

        //2.查询redis中文章是否存在
        ApArticle article = apArticleService.getById(result.getArticleId());
        ApArticle cache = new ApArticle();
        cache.setId(article.getId());
        cache.setTitle(article.getTitle());
        cache.setImages(article.getImages());
        cache.setAuthorId(article.getAuthorId());
        cache.setAuthorName(article.getAuthorName());
        cache.setLayout(article.getLayout());
        cache.setStaticUrl(article.getStaticUrl());
        //2.1 更新首页缓存数据
        String redisKeyPrefix = "leadnews:article:hot:page:";
        String indexKey = redisKeyPrefix + "0"; //前缀加0 是首页key
        String channelKey = redisKeyPrefix + article.getChannelId(); //频道id
        String cacheStr = JSON.toJSONString(cache);
        Double score = redisTemplate.opsForZSet().score(indexKey, cacheStr);  // 根据cacheStr对象去查    分页在redis的ZSET结构里so .opsForZSet() 是ZSET 非SET
        //2.1 文章不存在-根据数据库中操作行为计算历史得分+本次增量得分 将文章存入缓存
        if (score == null) { //表目前缓存里的文章根本不存在 则存   缓存里没得分 找历史得分
            int historyScore = computeScore(article); //历史得分
            redisTemplate.opsForZSet().add(indexKey, cacheStr, historyScore + plusScore); //历史得分+本次增量得分
            redisTemplate.opsForZSet().add(channelKey, cacheStr, historyScore + plusScore);
        } else {
            //2.2 文章存在-更新redis缓存中元素得分：追加本次操作增量得分
            redisTemplate.opsForZSet().incrementScore(indexKey, cacheStr, plusScore);
            redisTemplate.opsForZSet().incrementScore(channelKey, cacheStr, plusScore);
        }

        //3.更新数据库中 文章表，阅读，点赞，评论，收藏等数值
        LambdaUpdateWrapper<ApArticle> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.eq(ApArticle::getId, article.getId());
        updateWrapper.setSql("views = views +" + result.getView());//累加 下面同样
        updateWrapper.setSql("likes = likes +" + result.getLike());//累加
        updateWrapper.setSql("comment = comment +" + result.getComment());//累加
        updateWrapper.setSql("collection = collection +" + result.getCollect());//累加
        apArticleService.update(updateWrapper);

    }

    /**
     * 计算当日的增量分数  当日的操作整体权重*3
     *
     * @param result
     * @return
     */
    private Long computeScore(ArticleAggregatorResult result) {
        Long score = 0L;
        score += result.getView() * 1 * 3;
        score += result.getLike() * 3 * 3;
        score += result.getComment() * 5 * 3;
        score += result.getCollect() * 8 * 3;
        return score;
    }
}
